// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: gekim

#pragma once

#include <rapidjson/document.h>
#include <rapidjson/rapidjson.h>

#include <string>

#include "coin/proto/coin_market_enums.pb.h"
#include "coin2/base/conversion.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed_util/book_builder.h"
#include "coin2/exchange/base/feed_util/trade.h"

namespace impl {
namespace {
const int kConsecutiveZeroSizeThreshold = 3;
}  // namespace
using coin2::exchange::base::market::ExchangeType;
using coin2::exchange::base::market::MarketType;

class CoincheckFeedUpdateParser {
 public:
  static bool ParseSnapshot(BookBuilder* book_builder_,
                            const rapidjson::Value& book_json,
                            int64_t timestamp) {
    auto tx = book_builder_->NewTransaction(timestamp);

    for (const auto& ask_obj : book_json["asks"].GetArray()) {
      auto ask_array = ask_obj.GetArray();
      double price = stod(ask_array[0].GetString());
      double size = stod(ask_array[1].GetString());
      tx.UpdateAsk(price, size);
    }

    for (const auto& bid_obj : book_json["bids"].GetArray()) {
      auto bid_array = bid_obj.GetArray();
      double price = stod(bid_array[0].GetString());
      double size = stod(bid_array[1].GetString());
      tx.UpdateBid(price, size);
    }

    if (book_builder_->AskSize() == 0 || book_builder_->BidSize() == 0) {
      tx.Clear();
      return false;
    }
    return true;
  }

  static bool ParseBookUpdate(BookBuilder* book_builder_,
                              const rapidjson::Value& book_json,
                              int64_t timestamp) {
    auto tx = book_builder_->NewTransaction(timestamp);

    if (book_json.HasMember("asks")) {
      for (const auto& ask_obj : book_json["asks"].GetArray()) {
        auto ask_array = ask_obj.GetArray();
        double price = stod(ask_array[0].GetString());
        double size = stod(ask_array[1].GetString());
        tx.UpdateAsk(price, size);
      }
    }

    if (book_json.HasMember("bids")) {
      for (const auto& bid_obj : book_json["bids"].GetArray()) {
        auto bid_array = bid_obj.GetArray();
        double price = stod(bid_array[0].GetString());
        double size = stod(bid_array[1].GetString());
        tx.UpdateBid(price, size);
      }
    }

    if (book_builder_->AskSize() == 0 || book_builder_->BidSize() == 0) {
      tx.Clear();
      return false;
    }
    return true;
  }

  static void ParseTrade(
      const rapidjson::GenericArray<false, rapidjson::Value>& trade_array,
      Trade* trade, int64_t timestamp) {
    trade->price = stod(trade_array[2].GetString());
    trade->fill_qty = stod(trade_array[3].GetString());
    trade->side = ParseTradeSide(trade_array[4].GetString());
    trade->timestamp = timestamp;
  }

    static void ParseTrade2(
      const rapidjson::GenericArray<false, rapidjson::Value>& trade_array,
      Trade* trade, int64_t timestamp) {
    trade->price = stod(trade_array[3].GetString());
    trade->fill_qty = stod(trade_array[4].GetString());
    trade->side = ParseTradeSide(trade_array[5].GetString());
    trade->timestamp = timestamp;
  }

  static coin2::exchange::feed::TradeSide ParseTradeSide(
      const std::string& side) {
    if (side == "buy") {
      return coin2::exchange::feed::TRADE_BUY_SIDE;
    } else if (side == "sell") {
      return coin2::exchange::feed::TRADE_SELL_SIDE;
    } else if (side.empty()) {
      return coin2::exchange::feed::TRADE_NO_SIDE;
    } else {
      throw std::runtime_error("[Coincheck] unknown side: " + side);
    }
  }
};
}  // namespace impl

using impl::CoincheckFeedUpdateParser;
